懂得如何寫出好測試的程式,對開發者技術能力提升有重大的影響。
雖然一開始我成為測試開發工程師是個意外,我不得不在短時間內迅速補齊寫測試的技能,但沒想到這段經歷卻對我產生如此深遠的影響。
在這段期間,我寫了大量的測試程式。由於撰寫過很多測試,我能輕易區分哪些程式碼容易寫測試,哪些則較為困難。隨著我對撰寫測試程式的掌握日益熟練,我發現自己在其他方面的程式開發能力也隨之提升。
順帶一提,前一篇文章提到我在第二次接觸測試開發時,團隊曾得出「只能針對關鍵部分補寫測試,其餘部分暫時不管」的結論。當時的情況是,codebase 中多數程式都沒有遵循「單一職責原則」,且依賴外部資源或缺乏明確的輸出值。
如今,我在寫程式時會優先考慮如何撰寫易於測試的程式,而這個小小的思維改變,大大降低了寫出「一團義大利麵」式程式碼的可能性。
在經歷大量的測試開發後,我逐漸體會到,寫測試程式不僅能讓你快速發現問題,還能改變你寫程式的方式。測試驅動開發 (TDD) 並不只是一種工具,而是一種思維模式,讓你更有架構性地思考與開發程式。
要測試的程式邏輯是單純的純函數,依賴輸入與輸出,沒有外部依賴。
function add(a, b) {
return a + b;
}
// 測試範例
const assert = require('assert');
assert.strictEqual(add(2, 3), 5); // 測試通過
assert.strictEqual(add(-1, 1), 0); // 測試通過
// 要測的函式
function fetchUserData(userId) {
const currentDate = new Date(); // 外部依賴
const apiEndpoint = `https://api.example.com/users/${userId}`;
return fetch(apiEndpoint)
.then(response => response.json())
.then(data => {
return {
user: data,
fetchedAt: currentDate
};
});
}
// 測試範例
fetchUserData(1).then(data => {
// 測試使用者資料是否正確
assert.strictEqual(data.user.name, "John Doe");
// 日期可能會變動,而影響測試結果。
assert.strictEqual(data.fetchedAt.toISOString(), "......");
});
// 要測的函式
function fetchUserData(userId, currentDate) {
const apiEndpoint = `https://api.example.com/users/${userId}`;
return fetch(apiEndpoint)
.then(response => response.json())
.then(data => {
return {
user: data,
fetchedAt: currentDate
};
});
}
// 測試範例
const assert = require('assert');
const mockDate = new Date("2024-01-01");
fetchUserData(1, mockDate).then(data => {
// 測試使用者資料是否正確
assert.strictEqual(data.user.name, "John Doe");
// 測試日期是否正確
assert.strictEqual(data.fetchedAt.toISOString(), mockDate.toISOString());
});
單一職責原則(Single Responsibility Principle, SRP)是 SOLID 原則中的一部分,該原則強調每個函式或類別應該只有一個明確的責任。
當一個函式遵循這個原則時,寫測試會變得更加容易,因為你只需要測試它的單一功能,而不會被過多的外部依賴或複雜邏輯所干擾。
依賴倒置原則(Dependency Inversion Principle, DIP)強調,高層模組不應該依賴於低層模組,兩者都應該依賴於抽象。
這意味著,我們可以使用「依賴注入」技術,將函式中的依賴(如 API 請求或時間)注入函式中,這樣在測試時就可以輕鬆地替換掉這些依賴。範例中,將 currentDate 作為參數注入的範例,正是依賴倒置原則的具體應用。